const { Readable, Writable } = require('../stream/lib/ours/index');
import fileio from "@ohos.fileio";

const flagDic = {
    a: 0o1 | 0o100 | 0o2000,
    ax: 0o1 | 0o100 | 0o200 | 0o2000,
    "a+": 0o2 | 0o100 | 0o2000,
    "ax+": 0o2 | 0o100 | 0o200 | 0o2000,
    as: 0o1 | 0o100 | 0o2000 | 0o4010000,
    "as+": 0o2 | 0o100 | 0o2000 | 0o4010000,
    r: 0o0,
    "r+": 0o2,
    "rs+": 0o2 | 0o4010000,
    w: 0o1 | 0o100 | 0o1000,
    wx: 0o1 | 0o100 | 0o2000 | 0o1000,
    "w+": 0o2 | 0o100 | 0o1000,
    "wx+": 0o2 | 0o100 | 0o2000 | 0o1000,
}

class ReadStream extends Readable {
    path;
    fd;
    pending;

    constructor(path, options = {}) {
        let constructOptions = {};
        constructOptions.highWaterMark = options["highWaterMark"] || 65536;
        constructOptions.defaultEncoding = options["encoding"] || "utf8";
        constructOptions.emitClose = options["emitClose"] || "true";
        constructOptions.autoDestroy = options["autoClose"] || "true";
        super(constructOptions);

        this.options = {};
        this.options["flags"] = options.flags || "r";
        this.options["encoding"] = options.encoding || "utf8";
        this.options["fd"] = options.fd || null;
        this.options["mode"] = options.mode || 0o666;
        this.options["autoClose"] = options.autoClose || true;
        this.options["emitClose"] = options.emitClose || true;
        this.options["start"] = options.start;
        this.options["end"] = options.end || Infinity;
        this.options["fs"] = options.fs || null;
        this.options["highWaterMark"] = options["highWaterMark"] || 65536;

        this.path = path;
        this.pending = true;
        this.fd = null;
        this.position = options.start || 0;

        this.openHandler();
        this.readyHandler();
        this.construct();

    }


    /**
     * supplementary constructor
     */
    construct() {
        if (this.options["fd"]) {
            this.fd = this.options["fd"];
            this.path = undefined;
        } else {
            try {
                this.fd = fileio.openSync(
                    this.path.toString(),
                    flagDic[this.options["flags"]],
                    this.options["mode"]
                );

                this.emit("open", this.fd);

            } catch (err) {
                throw err;
            }

        }
    }


    /**
     * 
     * @param {number} n  Number of bytes to read
     * Private function called by build-in function of Readable class
     * to read a chunk of buffer from target file ,migth be called several times in a stream
     */
    _read(n) {
        try {
            let size = n || 4096;
            let arrbuf = new ArrayBuffer(size);
            let buf = Buffer.from(arrbuf);
            let readOptions = {};
            readOptions["offset"] = 0;
            readOptions["position"] = this.position;
            readOptions["length"] = this.options["end"]
                ? Math.min(
                    this.options["end"] - this.position,
                    this.options.highWaterMark)
                : this.options.highWaterMark;
            console.log("NODEFORPOLYFILL------" + JSON.stringify(readOptions));

            let bytesRead = fileio.readSync(this.fd, arrbuf, readOptions);
            this.position += bytesRead;

            this.push(bytesRead > 0 ? buf.slice(0, bytesRead) : null);

        } catch (err) {
            this.destroy(err);
        }
    }


    /**
     * 
     * @param {Error|null} err: Pass- in Error to indicate the reason to destroy the stream 
     * @param {function} callback
     * Private function called by build-in function of Readable class 
     * to destroy stream . will activate 'close  event and , if error occurs,'error' event. 
     */
    _destroy(err, callback) {
        if (typeof callback != "function") {
            callback = function (err) {
                if (err) {
                    throw err;
                }
            };
        }
        if (this.fd) {
            fileio.close(this.fd, (er) => {
                callback(er || err);
            });
        } else {
            callback(err);
        }
    }

    openHandler() {
        this.on("open", (fd) => {
            this.emit("ready");
        });
    }

    readyHandler() {
        this.on("ready", () => {
            this.pending = false;
        });
    }

    /**
     * 
     * @param {function} callback 
     * Manually close the readstream
     */
    close(callback) {
        if (typeof callback != "function") {
            callback = function (err) {
                if (err) {
                    throw err;
                }
            };
        }
        this.destroy(null, callback);
    }

}


class WriteStream extends Writable {

    /**
     * {string} absolute path of the target file of stream
     */
    path;

    /**
     * {number} File descriptor of the target file of stream
     */
    fd;

    /**
     * {boolean} True before 'ready' event is activated
     */
    pending;

    /**
     * {number} Cumulative bytes that stream has written
     */
    bytesWritten;



    constructor(path, options) {
        let constructOptions = {};

        constructOptions.defaultEncoding = options["encoding"] || "utf8";
        constructOptions.emitClose = options["emitClose"] || "true";
        constructOptions.autoDestroy = options["autoClose"] || "true";
        super(constructOptions);

        this.options = {};
        if (!options) {
            options = {};
        }

        this.options["flags"] = options.flags || "w";
        this.options["encoding"] = options.encoding || "utf8";
        this.options["fd"] = options.fd || null;
        this.options["mode"] = options.mode || 0o666;
        this.options["autoClose"] = options.autoClose || true;
        this.options["emitClose"] = options.emitClose || true;
        this.options["start"] = options.start;
        this.options["fs"] = options.fs || null;

        if (this.options["encoding"] != 'utf8') {
            throw "Only utf8 is supported in HarmonyOS!";
        }


        this.path = path;
        this.pending = true;
        this.fd = null;

        this.openHandler();
        this.readyHandler();
        this.bytesWritten = 0;
        this.construct();

    }


    /**
    * supplementary constructor
    */
    construct() {
        if (this.options["fd"]) {
            this.fd = this.options["fd"];
            this.path = undefined;
        } else {
            try {
                this.fd = fileio.openSync(
                    this.path.toString(),
                    flagDic[this.options["flags"]],
                    this.options["mode"]
                );

                this.emit("open", this.fd);

            } catch (err) {
                console.error(err);
                throw err;
            }

        }
    }


    /**
     * 
     * @param {string|buffer} chunk,String or Buffer to write 
     * @param {*} encoding  Encoding of 'chunk' if it is a string
     * @param {*} callback  Function to deal with error
     * 
     * private function called by build-in function of Writeable calss
     * to write a chunk of string or buffer to target file
     */
    _write(chunk, encoding, callback) {
        if (encoding) {
            if (typeof encoding == "function") {
                callback = encoding;
            } else {
                if (encoding != 'utf8' && encoding != 'buffer') {
                    throw 'Only utf8 and buffer is supported to write in HarmonyOs!';
                }

                if (typeof callback != 'function') {
                    callback = function (err) {
                        if (err) {
                            throw err;
                        }
                    };
                }
            }

            let writeOptions = {};
            if (this.options['start']) {
                writeOptions.position = this.options["start"];
            }

            fileio.write(
                this.fd,
                chunk.toString(),
                writeOptions,
                (err, bytesWritten) => {
                    if (err) {
                        console.error(err);
                        callback(err);
                    } else {
                        this.bytesWritten += bytesWritten;
                        callback(null);
                    }

                }
            )
        }
    }


    /**
     * 
     * @param {Error|null} err: Pass- in Error to indicate the reason to destroy the stream 
     * @param {function} callback
     * Private function called by build-in function of Readable class 
     * to destroy stream . will activate 'close  event and , if error occurs,'error' event. 
     */
    _destroy(err, callback) {
        if (typeof callback != "function") {
            callback = function (err) {
                if (err) {
                    throw err;
                }
            };
        }
        if (this.fd) {
            fileio.close(this.fd, (er) => {
                callback(er || err);
            });
        } else {
            callback(err);
        }
    }

    openHandler() {
        this.on("open", (fd) => {
            this.emit("ready");
        });
    }

    readyHandler() {
        this.on("ready", () => {
            this.pending = false;
        });
    }

    /**
        * 
        * @param {function} callback 
        * Manually close the readstream
        */
    close(callback) {
        if (typeof callback != "function") {
            callback = function (err) {
                if (err) {
                    throw err;
                }
            };
        }
        this.destroy(null, callback);
    }

}

module.exports = {
    ReadStream,
    WriteStream,
};